import { parseTime} from '@/utils';
import { defineStore } from 'pinia'
import {reactive} from 'vue';
import { U2, humanizeTimespanSec } from '@/utils/u';
import type { L0 } from '@/utils/u';
 import { getToken } from '@/utils/auth';
import 'reflect-metadata';
import { Type, plainToInstance, Transform } from 'class-transformer';
import request from '@/utils/request';
import J02 from '@/c02-posts.tmp.json';
import { decodeUnicodeEscapes } from 'unicode-escapes';
// Classes corresponding to pseudo-class.kt
export function sleep(sec){
    return new Promise((resolve, reject) => {setTimeout(() => {resolve();}, sec * 1000);});
}

export abstract class S {
    static InitialInput = "InitialInput";
    static TextSoW = "TextSoW";
    static TestCase = "TestCase";
    static NetworkSetup = "NetworkSetup";
    static TestScript = "TestScript";
    static TestResult = "TestResult";

    static Todo = "Todo";
    static Queue = "Queue";
}

export class ChainItem {
    new0 : boolean = false;
    // "newly" created item, when marked as "to be deleted", just remove it. 🦜 : this is a front-end only temperary flag.

    notes: string = "";// a description of the item
    klass: string = ""; // "InitialInput"[maroon] |  "TextSoW"[red] | "NetworkSetup"[orange] | "TestCase"[yellow] | "TestScript"[blue] | "TestResult"[purple]
    logs: string = "";
    h: string = "";// history

    // @ts-ignore
    @Transform(({ value }) => new Map(Object.entries(value)), { toClassOnly: true })
    P: Map<string, string> = new Map(); // <key, value>

    add_F(s: string) {// F: flag
        this.h = this.h + ";" + s + " " + new Date().toISOString() + " " + getToken();
    }
    add_greyed_F() {this.add_F("greyed");}
    add_greened_F() {this.add_F("man_greened");}
    add_new0_F() {this.add_F("created");}
    get logsL(): L0[] {
        let o =  U2.parseLogs(this.logs, ["info", "debug", "error", "warn", "log"]);
        // 🦜 : for each content: decode escaped unicode
        for (let i = 0; i < o.length; i++) {
            // console.log("🦜 : logsL: ", o[i]);
            // o[i].s = decodeUnicodeEscapes(o[i].s);
            // 🦜 : for now, we mock the decoding
            o[i].s = decodeUnicodeEscapes(o[i].s).replace(/\\n/g, '\n');
            // decode unicode escapes and 
        }
        return o;
        // 🦜 : for now, mock it
        // return [
        //     {t: new Date(), s: this.logs},
        //     {t: new Date(), s: "Log message 2\\nwith new \nline1 and **markdown**"},
        //     {t: new Date(), s: "Log message 3"}
        // ]
    }

    get notesD(): string {
        if (this.notes.trim() === "") {
            return "产物" + this.klass;
        }
        return this.notes.trim();
    }


     get grey(): boolean {return this.h.includes("greyed");}
     get green(): boolean {return this.h.includes("greened");}
     get color(): string {
         return TestChainInfo.colorPFromKlass(this.klass);
     }

    static Copy(c: ChainItem): ChainItem {
        const c0 = new ChainItem();
        // 1.1 shadow clone
        Object.assign(c0, c); // shallow copy
        // 1.2 clone the Map P
        c0.P = new Map(c.P); // clone the Map
        return c0;
    }

 }

export class TestChainInfo {
    constructor(
        public cid: string, public projectId: string,
        public ct: number, public projectName: string,
        public ed: string, // ed: "" not being edited, "<user-token>",
        public N: number, // length of chain items
        public creator: string,
        public createdAt: string, // ISO date string
        public finishedAt: string, // ISO date string
        public lastKlass: string,
        public status: string
    ) {}

    get creatorD(): string {
        // console.log("creatorD: ", this.creator);
        return this.creator || "<unknown>";
    }

    get done(): boolean {
        let s = this.status.trim().toLowerCase();
        return s.startsWith("done") || s.startsWith("error");
    }

    get finishedAtD(): string {
        // console.log("createdAtD: ", this.createdAt);
        let x =  new Date(this.finishedAt);
        if (x == 'Invalid Date') return this.finishedAt;
        // @ts-ignore
        return parseTime(x, '{y}-{m}-{d} 星期{a} {h}:{i}:{s}')
    }

    get createdAtD(): string {
        // console.log("createdAtD: ", this.createdAt);
        let x =  new Date(this.createdAt);
        if (x == 'Invalid Date') return this.createdAt;
        // @ts-ignore
        return parseTime(x, '{y}-{m}-{d} 星期{a} {h}:{i}:{s}')
    }
    get paused(): boolean {
        return this.ct <= 0; // ct: current time counter, negative means paused
    }
    get statusD(): string {
        // console.log("statusD: ", this.status, this.paused, this.ct);
        if (this.status === S.Todo) {
            return this.paused ? "paused" : `to be auto-greened in ${humanizeTimespanSec(this.ct * 15)}`;
        }else if (this.status.startsWith(S.Queue)) {
            return "In queue";
        }else {
            return this.status;
        }
    }
            static colorPFromKlass(klass: string): string {
            switch (klass) {
                case S.InitialInput: return "#b33e3eff"; // Maroon
                case S.TextSoW: return "#ff5050ff"; // Red
                case S.NetworkSetup: return "#f0c330"; // Yellow
                case S.TestCase: return "#fabb33ff"; // Orange
                case S.TestScript: return "#34a4cf"; // Blue
                case S.TestResult: return "#a634cf"; // Purple
                default: return "#000000"; // Unknown color
            }
        }
    get colorD(): string {
        const a = ['#cf3e34', '#f7a72b', '#f0c330', '#3e9b34', '#34a4cf', '#a634cf'];
        // return a[Math.floor(Math.random() * a.length)];
        return TestChainInfo.colorPFromKlass(this.lastKlass);
    }
}

export class TChain {
    constructor(public cid: string) {}
    // @ts-ignore
    @Type(() => ChainItem)
    L: ChainItem[] = []; // List of ChainItem
    static fromPlain(o: any): TChain {
        return plainToInstance(TChain, o);
    }                           // 🦜 : we already got a toJson Method~

    get i(): ChainItem | null {
        if (this.L.length === 0 || this.L[0].klass !== S.InitialInput) {
            return null; // No initial input item found
        }
        return this.L[0]; // Return the first item as the initial input
    }

    get projectId(): string {
        return this.i?.P.get("projectId") || "<none>";
    }
    get projectName(): string {
        return this.i?.P.get("projectName") || "<none>";
    }

    verify():string {
        // return "" | "<error message>"
        if (this.L.length === 0) {
            return "Chain is empty, please add an initial input item.";
        }
        /* 🦜 : For now, we only want to issue the cases of
           + Case I: <blue[Script]> + no previous <orange [NetworkSetup]> <2025-07-24 Thu>
        */
        let s = this.v01_blue();
        return s;
    }

    v01_blue(): string {
        const [i, o] = this.findLastNonGreyItem();
        if (o && o.klass === S.TestScript) {
            // Check if there is a previous NetworkSetup item
            for (let j = i - 1; j >= 0; j--) {
                if (this.L[j].klass === S.NetworkSetup && !this.L[j].grey) {
                    return ""; // Found a valid NetworkSetup item before TestScript
                }
            }
            return "TestScript item found without a valid preceding NetworkSetup item.";
        }
        return "";              // not my job
    }

    findLastNonGreyItem(i: number = -1): [number, ChainItem | null]{
        // if i == 0: return (0, null);
        // if i == 4: check for 0,1,2,3. We have the result L[i] == o
        if (i < 0) i = this.L.length;
        let o: ChainItem | null = null;
        while (i > 0) {
            i--;
            const o1 = this.L[i];
            if (!o1.grey) {
                o = o1;
                break;
            }
        }
        return [i, o];
    }

    getApprovedRate(klass: string, payload: string): object | string | number {
        // 1. find the classes
        let o = this.L.filter((x: ChainItem) => (x.klass === klass));
        console.log("getApprovedRate: ", o, "size", o.length);
        // 1.1 if none:
        if (o.length === 0){
            return `No item of klass=${klass} found`
        }else if (o.length === 1){
            let s1 = o[0].P.get(payload);
            if (!s1) {
                return `No payload=${payload} found in item of klass=${klass}`;
            }
            return s1.split('\n').length; // return the number of lines in the payload
        }
        // else more then two items, get the last two
        let s1 = o[o.length - 1].P.get(payload);
        let s2 = o[o.length - 2].P.get(payload);
        if (!s1 || !s2) {
            return `No payload=${payload} found in items of klass=${klass}`;
        }
        // 2. get the diff ratio and return
        return TChain.getDiffRatio(s1, s2);
    }

    getApprovedRateDZh(klass: string, payload: string): string {
        if (!["pyscript",'topox','conftest'].includes(payload)) { // 🦜 : for now, let's just do these
            return "";
        }
        // 1. get the approved rate
        let o = this.getApprovedRate(klass, payload);
        if (typeof o === 'string') {
            return o; // return the error message
        }else if (typeof o === 'number') {
            return `${klass}.${payload} 无历史中间物： 通过率：100%`;
        }

        // 2. format the result
        return `${klass}.${payload}： 最新产物:${o.n1}行，上个产物:${o.n2}行，共同行:${o.n3}行，采纳率:${Math.round(o.r1 * 100)}%，通过率:${Math.round(o.r2 * 100)}%`;
    }

    static getDiffRatio(s1:string,s2:string){
        // 1. split lines and turn into sets
        let l1 = new Set<string>(s1.split('\n'));
        let l2 = new Set<string>(s2.split('\n'));

        // 2. find common lines
        let l3 = new Set<string>();
        for (let l of l1) {
            if (l2.has(l)) {
                l3.add(l);
            }
        }

        // 3. return the stats object:
        return {
            n1: l1.size,            // number of lines in s1
            n2: l2.size,
            n3: l3.size,
            r1: l3.size / l1.size,  // ratio of common lines to s1
            r2: l3.size / l2.size,
        }
    }

    status(): string {
        return "<mocked-status>"
    }


    toJson(): string {
        return JSON.stringify(this, (key, value) => {
            if (key === 'new0') {
                return undefined; // This will exclude the new0 field from serialization
            }
            if (value instanceof Map) {
                return Object.fromEntries(value);
            }
            return value;
        }, 2);
    }

    static mock(cid: string): TChain {
        // Create initial input with mock data
        let projectId = `project-hi-${cid}`;
        let projectName = `A Test Project of ${cid}`;
        let notes = "AI-generated test suite with comprehensive coverage";

        const c = new TChain(cid);

        // Add some cool mock chain items
        const i0 = new ChainItem();
        i0.klass = S.InitialInput;
        i0.notes = "Initial input for the test chain";
        i0.P.set("projectId", projectId);
        i0.P.set("projectName", projectName);
        // i0.P.set("sowDocxName", "Statement of Work.docx"); <2025-07-20 Sun> 🦜 : maybe not necessary for now..
        // i0.P.set("cliDocxName", "Command Line Interface.docx");
        i0.P.set("sowDocx64", "SGVsbG8=");
        i0.P.set("cliDocx64", "SGVsbG8=");

        const i1 = new ChainItem();
        i1.klass = S.TextSoW;
        i1.notes = "Statement of Work validated and approved";
        i1.P.set("processedSow", "Example processed SOW content");
        i1.P.set("processedCli", "Example processed CLI content");
        i1.P.set("processedNotes", "Example processed notes content");
        
        const i2 = new ChainItem();
        i2.klass = S.TestCase;
        i2.notes = "Auto-generated test cases pending review";
        const steps = [
            {"step": "Initialize environment", "expected": "Environment ready"},
            {"step": "Run test suite", "expected": "All tests executed"},
            {"step": "Collect results", "expected": "Results aggregated successfully"}
        ];
        i2.P.set("name", "Auto-generated Test Suite");
        i2.P.set("test_steps", JSON.stringify(steps));
        
        const i3 = new ChainItem();
        i3.klass = S.NetworkSetup;
        i3.notes = "Network topology configured successfully";
        i3.P.set("id", "net-setup-123");
        i3.P.set("topox", "Example network topology content");
        i3.P.set("conftestPy", "Example conftest.py content");

        const i4 = new ChainItem();
        i4.klass = S.TestScript;
        i4.notes = "Python script generated for test execution";
        i4.P.set("pyscript", "print('Hello, World!')\n# Example Python script content");
        
        const i5 = new ChainItem();
        i5.klass = S.TestResult;
        i5.add_greyed_F(); // mark as greyed
        i5.notes = "Critical test failure detected - requires immediate attention";
        i5.P.set("ok", "true");
        i5.P.set("logs", "Example test log content");
        
        c.L = [i0, i1, i2, i3,i4, i5];
        // console.log("Mocked chain created:\n", c.toJson());
        return c;
    }
}


export class W<T> {
    constructor(
        public code: number,
        public data: T
    ) {}
    
    static from<T>(o: T): W<T> {
        return new W<T>(0, o);
    } 
    ok() : boolean {
        return this.code === 0;
    }
}

function PromiseU<T>(o: T, delay_s: number = 0): Promise<T> {
    return new Promise((resolve, reject) => {
        setTimeout(() => {resolve(o);}, delay_s * 1000);
    });
}

function PromiseU0<T>(o: T, delay_s: number = 0): Promise<W<T>>{
    return PromiseU(W.from(o), delay_s);
}

// https://axios-http.com/docs/req_config
/*
// `data` is the data to be sent as the request body
// Only applicable for request methods 'PUT', 'POST', 'DELETE', and 'PATCH'
// When no `transformRequest` is set, must be of one of the following types:
// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
// - Browser only: FormData, File, Blob
// - Node only: Stream, Buffer
*/
// export async function r01newTestProject(i: InitialInput): W<string> {
//     let _ = await request({url: '/R01_newTestProject', method: 'post', data: await i.toJson()});   // 🦜 will throw for us
//     return W.from("ok");
// }
// <2025-07-29 Tue> 🦜 : we remove this Api, trying to replace its functionality with r06postChain

// GET: get all chains, or just the todo ones
interface PG {q02_totalCount: number; q01_pageIdx: number; q03_pageSize: number;
              q04_projectName?: string;}
// type PG2<T> = [T[], number, number]; // (current page items, total items, total pages)
export interface PG2<T> {
    l: T[]; // current page items
    q02_totalCount: number; // total items
    totalPages: number; // total pages
}
export function pageIt<T>(l: T[], N: number, i: number): PG2<T> {
    i -= 1; // convert to 0-based index
    const T1 = l.length;        // total items
    const T0 = Math.ceil(T1 / N); // total pages
    const s0 = i * N;
    const s1 = s0 + N;
    const p = l.slice(s0, s1); // current page items
    return {l: p, q02_totalCount: T1, totalPages: T0};
}

function m01_mockAllChainInfo(): TestChainInfo[]{
    // 1. get the mocked data
    const st = [S.Todo,"Queue n/N"];
    // let r = await fetch('https://jsonplaceholder.typicode.com/posts')
    // r = await r.json();
    let r = J02; // use the mocked data from c02-posts.tmp.json
    // console.log("🦜 loaded r from json: ", r);
    let mc: TestChainInfo[] = [];
    // for (let i = 0; i < 100; i++) {
    //     const status = st[Math.floor(Math.random() * st.length)];
    for (let i = 0; i < Math.min(200, r.length); i++) {
        if (r[i].title.length > 20) {
            r[i].title = r[i].title.substring(0, 20) + "..."; // truncate title for display
        }
        mc.push(new TestChainInfo(
            `chain-${i}`,                // cid
            `proj-${i % 5}`,                 // projectId
            i % 5,  // ct
            `TestProject ${i}: ${r[i].title}`,            // projectName
            (i % 3 === 0 ) ? "" : `user-${i % 3}`, // ed (as a placeholder)
            0,                           // N (length of chain items, mocked as 0)
            "creator",                   // creator (mocked)
            new Date().toISOString(),    // createdAt (current date/time)
            new Date().toISOString(),    // createdAt (current date/time)
            S.InitialInput,
            st[Math.floor(Math.random() * st.length)] // status
            ));
    }
    return mc;
}
function m02_mockGetChainInfo(todo: boolean, pg: PG): W<PG2<TestChainInfo>> {
    // 1. get all
    let mc = m01_mockAllChainInfo();
    // 2. filter by todo status
    mc = todo ? mc.filter(c => c.status === S.Todo) : mc;
    // 3. filter by project name if provided
    if (pg.q04_projectName && pg.q04_projectName.trim() !== "") {
        let s = pg.q04_projectName.trim().toLowerCase();
        mc = mc.filter(c => c.projectName.toLowerCase().includes(s));
    }

    // 3. simulate pagination
    let o = pageIt(mc, pg.q03_pageSize, pg.q01_pageIdx);
    mc = o.l; // current page items
    console.log("r02getChainInfos: ", mc);
    return o;
}

export async function r02getChainInfos(todo: boolean, pg: PG): W<PG2<TestChainInfo>> {
    let o = await request({url: '/R02_getRChainInfos', method: 'get',
                           params: {todo: todo, q01_pageIdx: pg.q01_pageIdx,q03_pageSize: pg.q03_pageSize,}
                          });   // 🦜 will throw for us
    await sleep(1);
    o = plainToInstance(W<PG2<TestChainInfo>>, o); // convert to PG2<TestChainInfo>
    for (let i = 0; i < o.data.l.length; i++) {
        // console.log("c: ", c);
        o.data.l[i] = plainToInstance(TestChainInfo, o.data.l[i]); // convert each item to TestChainInfo
    }
    console.log("🦜 r02getChainInfos: ", o);
    return o;
    // return W.from(m02_mockGetChainInfo(todo, pg));
}

// GET: pause a chain (notify)
export async function r03pauseChain(cid: string): W<string> {
    let o = await request({url: '/R03_pauseChain', method: 'get', params: {cid:cid}});   // 🦜 will throw for us
    await sleep(1);
    o = plainToInstance(W<string>, o); // convert to W<string>
    return o;
    // return PromiseU0(true, 2);
}

// GET: resume a chain (notify)
export async function r04resumeChain(cid: string): W<boolean> {
    let o = await request({url: '/R04_resumeChain', method: 'get', params: {cid:cid}});   // 🦜 will throw for us
    await sleep(1);
    o = plainToInstance(W<string>, o); // convert to W<string>
    return o;
    // return PromiseU0(true, 2);
}

// GET: get a chain by id (for edit)
export async function r05getChainById(cid: string): W<TChain | null> {
    let o = await request({url: '/R05_getChainById', method: 'get', params: {cid:cid}});   // 🦜 will throw for us
    o = plainToInstance(W<string>, o); // convert to W<TChain>
    // 0.1 unbag the json data in W:
    o.data = JSON.parse(o.data);
    console.log("🦜 r05getChainById: ", o);
    o.data = TChain.fromPlain(o.data); // convert data to TChain
    console.log("🦜 r05's instance: ", o);
    await sleep(1);
    return o;
    // return W.from(TChain.mock(cid));
}

// POST: add/update a chain item

// export async function r01newTestProject(i: InitialInput): W<string> {
//     let _ = await request({url: '/R01_newTestProject', method: 'post', data: await i.toJson()});   // 🦜 will throw for us
//     return W.from("ok");
// }
export async function r06postChain(ch: TChain): W<string> {
    const s = ch.toJson();
    // console.log("🦜 r06postChain: ", s);
    let _ = await request({url: '/R06_postChain', method: 'post', data:s });   // 🦜 will throw for us
    await sleep(1);
    return W.from("ok");
}


export const useAaaStore = defineStore('aaa', () => {
    const M = reactive(new Map<string,ChainItem>()); // name -> ChainItem
    function add(n: string, c: ChainItem) {
        // 1. clone the item to avoid mutation
        M.set(n, ChainItem.Copy(c));
    }
    function del(n: string) {
        console.log("del", n);
        M.delete(n);
    }
    function ls(k: string): string[] {
        // return all keys whose klazz = k
        const l: string[] = [];
        M.forEach((v, k1) => {
            console.log(`ls ${k1} -> ${v}`);
            if (v.klass === k) {
                l.push(k1);
            }
        });
        return l;
    }
    return {M, add, del, ls};
});
